package com.darren.framework.utils;

import com.alibaba.fastjson.JSONObject;
import com.darren.framework.Exception.TokenException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.util.Date;

public class JWTUtil {

    private static Logger logger = LogManager.getLogger(JWTUtil.class);

    /**
     * Access Token 的密钥
     */
    private static String ACCESS_SECRET = "AB1E4F3EA534567890A2ECE3245AFD";

    /**
     * Refresh Token 的密钥
     */
    private static String REFRESH_SECRET = "BAA2277AE3BDE436A40E2AC5F4AB72D5";

    /**
     * Access Token 的有效期时长，默认15分钟
     */
    private static long ACCESS_MAX_AGE = 900000L;

    /**
     * Refresh Token 的有效期时长，默认半小时
     */
    private static long REFRESH_MAX_AGE = 1800000L;

    /**
     * Access Token 的加密方式
     */
    private static SignatureAlgorithm ACCESS_ALGORITHM;

    /**
     * Refresh Token 的加密方式
     */
    private static SignatureAlgorithm REFRESH_ALGORITHM;

    /**
     * jwt签发者
     */
    private static String ISSUER = "Archive";

    /**
     * 设置jwt签发者
     *
     * @param issuer
     */
    public static void setIssuer(String issuer) {
        ISSUER = issuer;
    }

    /**
     * 设置Access 有效期时长
     *
     * @param accessMaxAge
     */
    public static void setAccessExpired(long accessMaxAge) {
        ACCESS_MAX_AGE = accessMaxAge;
    }

    /**
     * 获取Access 有效期时长
     *
     * @return
     */
    public static long getAccessExpired() {
        return ACCESS_MAX_AGE;
    }

    /**
     * 设置Refresh 有效期时长
     *
     * @param refreshMaxAge
     */
    public static void setRefreshExpired(long refreshMaxAge) {
        REFRESH_MAX_AGE = refreshMaxAge;
    }

    /**
     * 获取Refresh 有效期时长
     *
     * @return
     */
    public static long getRefreshExpired() {
        return REFRESH_MAX_AGE;
    }

    /**
     * 设置Access Token 的密钥
     *
     * @param accessSecret
     */
    public static void setAccessSecret(String accessSecret) {
        ACCESS_SECRET = accessSecret;
    }

    /**
     * 设置Refresh Token 的密钥
     *
     * @param refreshSecret
     */
    public static void setRefreshSecret(String refreshSecret) {
        REFRESH_SECRET = refreshSecret;
    }

    /**
     * 设置加密方式
     */
    static {
        ACCESS_ALGORITHM = SignatureAlgorithm.HS512;
        REFRESH_ALGORITHM = SignatureAlgorithm.HS384;
    }

    /**
     * 生成Access Token
     *
     * @param payload
     * @return
     */
    public static <T> String signAccessJWT(T payload) {
        return signAccessJWT(payload, ACCESS_MAX_AGE);
    }

    /**
     * 生成Access Token
     *
     * @param payload
     * @param maxAge  0 表未永不过期
     * @return
     */
    public static <T> String signAccessJWT(T payload, long maxAge) {
        long expTime = maxAge >= 0 ? maxAge : ACCESS_MAX_AGE;
        return signJWT(payload, ACCESS_ALGORITHM, ACCESS_SECRET, expTime);
    }

    /**
     * 生成Refresh Token
     *
     * @param payload
     * @return
     */
    public static <T> String signRefreshJWT(T payload) {
        return signRefreshJWT(payload, ACCESS_MAX_AGE);
    }

    /**
     * 生成Refresh Token
     *
     * @param payload
     * @param maxAge  0 表未永不过期
     * @return
     */
    public static <T> String signRefreshJWT(T payload, long maxAge) {
        long expTime = maxAge >= 0 ? maxAge : REFRESH_MAX_AGE;
        return signJWT(payload, REFRESH_ALGORITHM, REFRESH_SECRET, expTime);
    }

    /**
     * 生成Token
     *
     * @param payload
     * @param signatureAlgorithm
     * @param secret
     * @param maxAge
     * @return
     */
    private static <T> String signJWT(T payload, SignatureAlgorithm signatureAlgorithm, String secret, long maxAge) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        byte[] secretBytes = DatatypeConverter.parseBase64Binary(secret);
        SecretKey signingKey = new SecretKeySpec(secretBytes, signatureAlgorithm.getJcaName());
        String payloadString = JSONObject.toJSONString(payload);
        // 添加构成JWT的参数
        JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
                .setIssuedAt(now) // iat: jwt的签发时间
                .setSubject(payloadString) // 主题 sub(Subject)：代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userid，roldid之类的，作为什么用户的唯一标志,也差不多是个人的一些信息
                .setIssuer(ISSUER) // jwt签发者
                .signWith(signatureAlgorithm, signingKey); // 估计是第三段密钥
        // 添加Token过期时间
        if (maxAge > 0) {
            // 过期时间
            long expMillis = nowMillis + maxAge;
            // 现在是什么时间
            Date exp = new Date(expMillis);
            // 系统时间之前的token都是不可以被承认的
            builder.setExpiration(exp)//exp: jwt的过期时间，这个过期时间必须要大于签发时间
                    .setNotBefore(now);//nbf: 定义在什么时间之前，该jwt都是不可用的
        }
        // 生成JWT字符串
        return builder.compact();
    }


    /**
     * 解密Access jwt
     *
     * @param jwt
     * @param clazz
     * @return
     */
    public static <T> T unSignAccessJWT(String jwt, Class<T> clazz) {
        return unSignJWT(jwt, clazz, ACCESS_ALGORITHM, ACCESS_SECRET);
    }

    /**
     * 解密Refresh jwt
     *
     * @param jwt
     * @param clazz
     * @return
     */
    public static <T> T unSignRefreshJWT(String jwt, Class<T> clazz) {
        return unSignJWT(jwt, clazz, REFRESH_ALGORITHM, REFRESH_SECRET);
    }

    /**
     * 解密 jwt
     *
     * @param jwtString
     * @param clazz
     * @param signatureAlgorithm
     * @param secret
     * @return
     */
    private static <T> T unSignJWT(String jwtString, Class<T> clazz, SignatureAlgorithm signatureAlgorithm, String secret) {
        logger.info(jwtString);
        byte[] encodedKey = DatatypeConverter.parseBase64Binary(secret);
        SecretKey key = new SecretKeySpec(encodedKey, signatureAlgorithm.getJcaName());// 根据给定的字节数组使用AES加密算法构造一个密钥，使用 encodedKey中的始于且包含 0 到前 leng 个字节这是当然是所有。（后面的文章中马上回推出讲解Java加密和解密的一些算法）
        try {
            Claims claims = Jwts.parser() // 得到DefaultJwtParser
                    .setSigningKey(key) // 设置签名的秘钥
                    .parseClaimsJws(jwtString).getBody();// 设置需要解析的jwt
            String payloadString = claims.getSubject();
            return JSONObject.parseObject(payloadString, clazz);
        } catch (IllegalArgumentException illegalArgumentException) {
            logger.error(illegalArgumentException.getMessage(), illegalArgumentException);
            throw new TokenException(TokenStatus.TOKEN_NULL_OR_EMPTY);
        } catch (MalformedJwtException malformedJwtException) {
            logger.error(malformedJwtException.getMessage(), malformedJwtException);
            throw new TokenException(TokenStatus.FORMAT_ERROR);
        } catch (SignatureException signatureException) {
            logger.error(signatureException.getMessage(), signatureException);
            throw new TokenException(TokenStatus.INVALID_SIGNATURE);
        } catch (ExpiredJwtException expiredJwtException) {
            logger.error(expiredJwtException.getMessage(), expiredJwtException);
            throw new TokenException(TokenStatus.EXPIRED);
        } catch (Exception exception) {
            logger.error(exception.getMessage(), exception);
            throw new TokenException(TokenStatus.UNKNOWN);
        }
    }

    /**
     * JWT Token 的状态
     *
     * @author Darren Zhou
     */
    public static enum TokenStatus {
        AVAILABLE("有效的token。", 10000),
        TOKEN_NULL_OR_EMPTY("token 不可以为空。", 10001),
        FORMAT_ERROR("token 格式错误！", 10002),
        INVALID_SIGNATURE("无效token签名。", 10003),
        INVALID_TOKEN("无效的token。", 10004),
        EXPIRED("token已过期。", 10005),
        UNKNOWN("未知错误。", 99999);

        // 成员变量
        private String message;
        private int code;

        // 构造方法
        private TokenStatus(String message, int code) {
            this.message = message;
            this.code = code;
        }

        // 普通方法
        public static String getMessage(int code) {
            for (TokenStatus status : TokenStatus.values()) {
                if (status.getCode() == code) {
                    return status.message;
                }
            }
            return null;
        }

        // get set 方法
        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public int getCode() {
            return code;
        }

        public void setCode(int code) {
            this.code = code;
        }
    }
}

